//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<cache_pool.h>
///	@path	~/src/lib/memory/
///	@date	2007/11/17
///	@desc	Scoped cache block.

#pragma once

#include "xeres.h"

namespace xeres {

	/*!
		\class	cache_pool
		\brief	Cache pool, memory can be allocated but cannot be freed.
				For scoped use, to allocate memory efficiently.
	*/
	template<
		typename _Allocator
	> class cache_pool
	{
	public:

		/// \ctor
		cache_pool( size_t page_capacity , const _Allocator& allocator = _Allocator() )
			: m_pageCapacity( page_capacity )
			, m_allocator( allocator )
			, m_rootPage( NULL )
			, m_fullPages( NULL )
		{
		}

		/// \dtor
		~cache_pool( void )
		{
			destroy_pages( m_rootPage );
			destroy_pages( m_fullPages );
		}

		/*!
			\brief		Allocate new cache block.
		*/
		void * allocate( size_t size )
		{
			if( m_rootPage == NULL )
				m_rootPage = new_page( m_pageCapacity );

			size = ALIGNOF( size , ALIGN_SIZE );

			_CachePage * p = m_rootPage;
			while( p->avail < size )
			{
				if( p->next == NULL )
				{
					p = new_page( size );
					p->next = m_rootPage;
					m_rootPage->prev = p;
				}
				p = p->next;
			}
			void * ptr = p->space;
			p->avail -= size;
			p->space += size;
			page_full( p );
			return ptr;
		}

	private:

		// pages
		struct ALIGNED _CachePage
		{
			byte *			ptr;	// allocated space base
			byte *			space;	// next available space
			size_t			total;	// total memory size
			size_t			avail;	// available size
			_CachePage *	prev;	// previous free cache
			_CachePage *	next;	// next cache page
			int				spare0[2]; // padding

			bool is_full( void ) const {
				return avail < ALIGN_SIZE;
			}
		};

		// make new page
		_CachePage * new_page( size_t size )
		{
			size = size > m_pageCapacity ? size : m_pageCapacity;
			_CachePage * np = static_cast<_CachePage*>( m_allocator.allocate( sizeof(_CachePage) + size ) );
			np->total = size;
			np->avail = size;
			np->ptr = static_cast<byte*>(np) + sizeof(_CachePage);
			np->space = np->ptr;
			np->prev = NULL;
			np->next = NULL;
			return np;
		}

		// page full
		void page_full( _CachePage * page )
		{
			if( !page->is_full() )
				return;
			if( page == m_rootPage )
				m_rootPage = NULL;
			if( page->next )
			{
				page->next->prev = page->prev;
			}
			if( page->prev )
			{// detach link
				page->prev->next = page->next;
			}
			page->prev = NULL;
			if( m_fullPages )
			{
				m_fullPages->prev = page;
				page->next = m_fullPages;
			}
			else
			{
				page->next = NULL;
			}
			m_fullPages = page;
		}

		// destroy pages
		void destroy_pages( _CachePage * pages )
		{
			while( pages )
			{
				_CachePage * p = pages;
				pages = p->next;
				m_allocator.deallocate( p , sizeof(_CachePage) + p->total );
			}
		}

		// root page
		_CachePage *		m_rootPage;
		_CachePage *		m_fullPages;

		// allocator
		size_t			m_pageCapacity;
		_Allocator		m_allocator;

	};

} // namespace xeres
